﻿using System;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace WcfDynamicProxy
{
    /// <summary>
    /// IChannelFactory over WCF
    /// </summary>
    /// <typeparam name="TService"></typeparam>
    internal class WcfChannelFactory<TService> : IChannelFactory<TService>, IDisposable
    {
        private dynamic channelFactory;

        #region Contructors

        public WcfChannelFactory(EndpointAddress address)
        {
            BuildChannelFactory(address);
        }

        public WcfChannelFactory(string endpointConfigurationName)
        {
            BuildChannelFactory(endpointConfigurationName);
        }

        public WcfChannelFactory(string endpointConfigurationName, EndpointAddress address)
        {
            BuildChannelFactory(endpointConfigurationName, address);
        }

        public WcfChannelFactory(Func<Uri, Uri> addressTranformer)
        {
            BuildChannelFactory(addressTranformer);
        }

        #endregion

        /// <summary>
        /// Gets or sets the address.
        /// </summary>
        internal EndpointAddress Address { get; set; }

        private void BuildChannelFactory(EndpointAddress address)
        {
            channelFactory = Activator.CreateInstance(GetChannelFactoryType(), new BasicHttpBinding(), address);
        }

        private void BuildChannelFactory(string endpointConfigurationName, EndpointAddress address)
        {
            channelFactory = Activator.CreateInstance(GetChannelFactoryType(), endpointConfigurationName, address);
        }

        private void BuildChannelFactory(string endpointConfigurationName)
        {
            channelFactory = Activator.CreateInstance(GetChannelFactoryType(), endpointConfigurationName);
        }

        private void BuildChannelFactory(Func<Uri, Uri> addressTranformer)
        {
            dynamic client = Activator.CreateInstance(GetClientType());
            ServiceEndpoint ep = client.Endpoint;
            ep.Address = new EndpointAddress(addressTranformer(ep.Address.Uri));

            channelFactory = client.ChannelFactory;
        }

        private static Type GetChannelFactoryType()
        {
            Type type = AsyncServiceInterfaceFactory.Instance.Generate(typeof(TService));
            return typeof(System.ServiceModel.ChannelFactory<>).MakeGenericType(type);
        }

        private static Type GetClientType()
        {
            Type type = AsyncServiceInterfaceFactory.Instance.Generate(typeof(TService));
            return typeof(ConcreteClientBase<>).MakeGenericType(type);
        }

        public IAsyncChannel<TService> CreateChannel()
        {
            return new AsyncChannel(channelFactory.CreateChannel());
        }

        #region IDisposable

        public void Dispose()
        {
            try
            {
                channelFactory.Close();
            }
            catch(Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Error closing channel " + ex);
            }
        }

        #endregion

        internal sealed class AsyncChannel : IAsyncChannel<TService>
        {
            internal readonly object channel;

            internal AsyncChannel(object channel)
            {
                this.channel = channel;
            }

            public object Channel
            {
                get { return channel; }
            }

            #region IDisposable

            public void Dispose()
            {
                ChannelDisposer.AsDisposable(channel).Dispose();
            }

            #endregion

            /// <summary>
            /// From http://thorarin.net/blog/post/2010/05/30/Indisposable-WCF-clients.aspx
            /// </summary>
            private class ChannelDisposer : IDisposable
            {
                private ICommunicationObject _channel;

                private ChannelDisposer(ICommunicationObject channel)
                {
                    _channel = channel;
                }

                public static IDisposable AsDisposable(object client)
                {
                    return new ChannelDisposer((ICommunicationObject) client);
                }

                public void Dispose()
                {
                    bool success = false;

                    try
                    {
                        if (_channel.State != CommunicationState.Faulted)
                        {
                            _channel.Close();
                            success = true;
                        }
                    }
                    finally
                    {
                        if (!success)
                            _channel.Abort();
                    }
                }
            }
        }
    }
}